package ripple.test.tools;

import java.io.File;
import java.io.FileWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import ripple.common.DebugTool;
import ripple.common.entity.NodeMetadata;

public class Analyse {

    public static void main(String[] args) throws Exception {
        // checkRepeatPort();

        File outDir = new File("out");
        File dataDir = new File("data");

        String outfilePathString = "out/latecy.txt";

        DebugTool.prepareDir(outDir);

        String applicationName = "testApp";
        String key = "test";

        Map<String, List<Integer>> latecyMapByNode = new ConcurrentHashMap<String, List<Integer>>();
        List<Integer> latecyList;

        Map<Integer, Integer> clientCountPerServer = new ConcurrentHashMap<>();

        Map<String, Set<Integer>> receiveFromWhere = new ConcurrentHashMap<>();

        latecyMapByNode = getValuableLatecyMapByNode(dataDir, applicationName, key);
        receiveFromWhere = getReceiveFromWhereMap(dataDir, applicationName, key);
        // System.out.println(sortNodeByRound(receiveFromWhere));
        // receiveFromWhere = getServerReceiveFromWhereMap(dataDir, applicationName, key);

        // outputLatecyByNode(getLatecyMapByNode(dataDir, applicationName, key), outfilePathString);
        outputLatecyAndFromWhereByNode(latecyMapByNode, receiveFromWhere, outfilePathString);
        // latecyMapByNode = filterLatecyMapByNode(latecyMapByNode, 95);
        // latecyMapByNode = filterLatecyMapByNode(latecyMapByNode, 96);
        // latecyMapByNode = filterLatecyMapByNode(latecyMapByNode, 42);
        // latecyMapByNode = filterLatecyMapByNode(latecyMapByNode, 67);
        // receiveFromWhere = filterFromWhereMapByNode(receiveFromWhere, 42);
        // receiveFromWhere = filterFromWhereMapByNode(receiveFromWhere, 67);
        // receiveFromWhere = filterFromWhereMapByNode(receiveFromWhere, 97);
        // receiveFromWhere = filterFromWhereMapByNode(receiveFromWhere, 98);
        // outputLatecyByRound(latecyMapByNode, sortNodeByRound(receiveFromWhere), outfilePathString);
        // outputStatsByRound(latecyMapByNode, sortNodeByRound(receiveFromWhere), outfilePathString);
        // List<Integer> statsout = statsValuableOverall(statsValuableByNode(latecyMapByNode));
        // outputOverallStatsOut(statsout, outfilePathString);
//         latecyList.sort((num1, num2) -> {
//             return num1.compareTo(num2);
        // int averageLatecy = latecyList.stream().reduce(Integer::sum).orElse(0) / latecyList.size();
        //    fileWriter.write("average: " + averageLatecy + "\n");
//         fileWriter.write("0.1: " + String.valueOf(latecyList.get((int) (latecyList.size() * 0.1))) + "\n");
//  
        //    fileWriter.write("0.25: " + String.valueOf(latecyList.get((int) (latecyList.size() * 0.25))) + "\n");
//         fileWriter.write("0.5: " + String.valueOf(latecyList.get((int) (latecyList.size() * 0.5))) + "\n");
//         fileWriter.write("0.75: " + String.valueOf(latecyList.get((int) (latecyList.size() * 0.75))) + "\n");
//         fileWriter.close();
    }

    public static boolean isDataBase(File databasePath) {
        if (databasePath.toString().split("\\.").length != 2
                || !databasePath.toString().split("\\.")[1].equals("db")) {
            System.out.println(databasePath.toString() + " is not a database");
            return false;
        }
        return true;
    }

    public static boolean isServer(String pathString) {
        if (pathString.contains("client")) {
            return false;
        }
        return true;
    }

    public static int getServerId(String pathString) {
        return Integer.parseInt(pathString.split("server-")[1].split("\\.")[0]);
    }

    public static Map<Integer, Integer> countClientPerServer(Map<Integer, Integer> map, String pathString) {
        if (isServer(pathString)) {
            int serverId = Integer.parseInt(pathString.split("server-")[1].split("\\.")[0]);
            map.putIfAbsent(serverId, 0);
        } else {
            int serverId = Integer.parseInt(pathString.split("server-")[1].split("-client")[0]);
            int clientCount = map.getOrDefault(serverId, 0);
            map.put(serverId, clientCount + 1);
        }
        return map;
    }

    public static List<String> getSortedList(Set<String> databaseSet) {
        List<String> list = new ArrayList<>();

        for (String databaseString : databaseSet) {
            if (isServer(databaseString)) {
                list.add(databaseString);
            }
        }

        list.sort(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                int serverId1 = Integer.parseInt(o1.split("server-")[1].split("\\.")[0]);
                int serverId2 = Integer.parseInt(o2.split("server-")[1].split("\\.")[0]);
                return serverId1 - serverId2;
            }
        });

        return list;
    }

    public static String setToString(Set<Integer> set) {
        String s = "";
        for (Integer from_where : set) {
            s = s + from_where + " ";
        }
        return s;
    }

    public static void checkRepeatPort() {
        List<NodeMetadata> nodeList = DebugTool.getNodeList();
        Set<Integer> set = new HashSet<>();
        for (NodeMetadata node : nodeList) {
            if (!set.add(node.getPort())) {
                DebugTool.debugInfo("[Analyse] checkRepeatPort() " + node.getPort() + " repeated.");
                return;
            }
        }
        DebugTool.debugInfo("[Analyse] checkRepeatPort() no node port repeated.");
    }

    public static String getDatabaseStringById(Set<String> stringSet, int id) {
        for (String string : stringSet) {
            if (getServerId(string) == id) {
                return string;
            }
        }
        return null;
    }

    public static Set<String> filterServer(Set<String> set) {
        Set<String> newSet = new HashSet<>();
        for (String string : set) {
            if (isServer(string)) {
                newSet.add(string);
            }
        }
        return newSet;
    }

    public static List<Set<Integer>> sortNodeByRound(Map<String, Set<Integer>> receiveFromWhere) {
        Set<Integer> unreachedNodeList = new HashSet<>();
        Set<Integer> reachedNode = new HashSet<>();
        Set<String> serverSet = filterServer(receiveFromWhere.keySet());
        for (String database : serverSet) {
            unreachedNodeList.add(getServerId(database));
        }
        List<Set<Integer>> nodeListByRound = new ArrayList<>();
        Set<Integer> nodeListCurrentRound = new HashSet<>();
        for (String database : serverSet) {
            int serverId = getServerId(database);
            if (!unreachedNodeList.contains(serverId)) {
                continue;
            }
            Set<Integer> from_where = receiveFromWhere.get(getDatabaseStringById(serverSet, serverId));
            if (from_where.contains(serverId)) {
                nodeListCurrentRound.add(serverId);
                unreachedNodeList.remove(serverId);
                reachedNode.add(serverId);
            }
        }

        nodeListByRound.add(nodeListCurrentRound);
        Set<Integer> nodeListNextRound = new HashSet<>();

        while (nodeListCurrentRound.size() != 0) {
            for (String database : serverSet) {
                int serverId = getServerId(database);
                if (!unreachedNodeList.contains(serverId)) {
                    continue;
                }
                Set<Integer> from_where = receiveFromWhere.get(getDatabaseStringById(serverSet, serverId));
                for (int lastRoundServerId : nodeListCurrentRound) {
                    if (from_where.contains(lastRoundServerId)) {
                        nodeListNextRound.add(serverId);
                        unreachedNodeList.remove(serverId);
                        reachedNode.add(serverId);
                        break;
                    }
                }
            }

            nodeListByRound.add(nodeListNextRound);
            nodeListCurrentRound = nodeListNextRound;
            nodeListNextRound = new HashSet<>();
            //     // System.out.println(nodeListCurrentRound);
        }
        return nodeListByRound;

    }

    // 未测试
    public static List<Map<String, List<Integer>>> sortLatecyByRound(Map<String, List<Integer>> latecyMapByNode,
            List<Set<Integer>> nodeListByRound) {
        List<Map<String, List<Integer>>> latecyMapByRound = new ArrayList<>();
        for (int i = 0; i < nodeListByRound.size(); i++) {
            Set<Integer> currentRoundNodelist = nodeListByRound.get(i);
            List<Integer> sortedCurrentRoundNodelist = new ArrayList<>();
            sortedCurrentRoundNodelist.addAll(currentRoundNodelist);
            Collections.sort(sortedCurrentRoundNodelist);
            Map<String, List<Integer>> currentRoundLatecyMap = new ConcurrentHashMap<>();
            for (int id : sortedCurrentRoundNodelist) {
                String databaString = getDatabaseStringById(latecyMapByNode.keySet(), id);
                List<Integer> latecyList = latecyMapByNode.get(databaString);
                currentRoundLatecyMap.put(databaString, latecyList);
            }
            latecyMapByRound.add(currentRoundLatecyMap);
        }
        return latecyMapByRound;

    }

    public static Map<String, List<Integer>> getLatecyMapByNode(File dataDir,
            String applicationName, String key) {
        Map<String, List<Integer>> latecyMapByNode = new ConcurrentHashMap<>();
        for (File database : dataDir.listFiles()) {
            if (!isDataBase(database)) {
                continue;
            }

            if (!latecyMapByNode.containsKey(database.toString())) {
                latecyMapByNode.put(database.toString(), new ArrayList<>());
            }

            List<Integer> latecyList = latecyMapByNode.get(database.toString());

            try {
                Connection connection = DriverManager.getConnection("jdbc:sqlite:" + database);
                String sql = "SELECT * FROM [message] WHERE [item_application_name] = ? AND [item_key] = ?;";
                PreparedStatement statement = connection.prepareStatement(sql);
                statement.setString(1, applicationName);
                statement.setString(2, key);
                ResultSet resultSet = statement.executeQuery();
                while (resultSet.next()) {
                    latecyList.add(resultSet.getInt("latecy"));
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return latecyMapByNode;
    }

    public static Map<String, List<Integer>> getValuableLatecyMapByNode(File dataDir,
            String applicationName, String key) {
        Map<String, List<Integer>> latecyMapByNode = new ConcurrentHashMap<>();
        for (File database : dataDir.listFiles()) {
            if (!isDataBase(database) || !isServer(database.toString())) {
                continue;
            }

            if (!latecyMapByNode.containsKey(database.toString())) {
                latecyMapByNode.put(database.toString(), new ArrayList<>());
            }

            List<Integer> latecyList = latecyMapByNode.get(database.toString());

            try {
                Connection connection = DriverManager.getConnection("jdbc:sqlite:" + database);
                String sql = "SELECT * FROM [message] WHERE [item_application_name] = ? AND [item_key] = ?;";
                PreparedStatement statement = connection.prepareStatement(sql);
                statement.setString(1, applicationName);
                statement.setString(2, key);
                ResultSet resultSet = statement.executeQuery();
                while (resultSet.next()) {
                    if (getServerId(database.toString()) != resultSet.getInt("from_where")) {
                        latecyList.add(resultSet.getInt("latecy"));
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return latecyMapByNode;
    }

    public static Map<String, List<Integer>> filterLatecyMapByNode(Map<String, List<Integer>> latecyMapByNode, int filteredId) {
        for (String fileString : latecyMapByNode.keySet()) {
            if (getServerId(fileString) == filteredId) {
                latecyMapByNode.remove(fileString);
                break;
            }
        }
        return latecyMapByNode;
    }

    public static Map<String, Set<Integer>> filterFromWhereMapByNode(
            Map<String, Set<Integer>> receiveFromWhere, int filteredId) {
        for (String fileString : receiveFromWhere.keySet()) {
            if (isServer(fileString) && getServerId(fileString) == filteredId) {
                receiveFromWhere.remove(fileString);
                break;
            }
        }
        return receiveFromWhere;
    }

    public static Map<String, Set<Integer>> getReceiveFromWhereMap(File dataDir,
            String applicationName, String key) {
        Map<String, Set<Integer>> receiveFromWhere = new ConcurrentHashMap<>();
        for (File database : dataDir.listFiles()) {
            if (!isDataBase(database)) {
                continue;
            }

            if (!receiveFromWhere.containsKey(database.toString())) {
                receiveFromWhere.put(database.toString(), new HashSet<Integer>());
            }

            Set<Integer> receiveFromWhereOfNode = receiveFromWhere.get(database.toString());

            try {
                Connection connection = DriverManager.getConnection("jdbc:sqlite:" + database);
                String sql = "SELECT * FROM [message] WHERE [item_application_name] = ? AND [item_key] = ?;";
                PreparedStatement statement = connection.prepareStatement(sql);
                statement.setString(1, applicationName);
                statement.setString(2, key);
                ResultSet resultSet = statement.executeQuery();
                while (resultSet.next()) {
                    receiveFromWhereOfNode.add(resultSet.getInt("from_where"));
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return receiveFromWhere;
    }

    public static Map<String, Set<Integer>> getServerReceiveFromWhereMap(File dataDir,
            String applicationName, String key) {
        Map<String, Set<Integer>> receiveFromWhere = new ConcurrentHashMap<>();
        for (File database : dataDir.listFiles()) {
            if (!isDataBase(database) && !isServer(database.toString())) {
                continue;
            }

            if (!receiveFromWhere.containsKey(database.toString())) {
                receiveFromWhere.put(database.toString(), new HashSet<Integer>());
            }

            Set<Integer> receiveFromWhereOfNode = receiveFromWhere.get(database.toString());

            try {
                Connection connection = DriverManager.getConnection("jdbc:sqlite:" + database);
                String sql = "SELECT * FROM [message] WHERE [item_application_name] = ? AND [item_key] = ?;";
                PreparedStatement statement = connection.prepareStatement(sql);
                statement.setString(1, applicationName);
                statement.setString(2, key);
                ResultSet resultSet = statement.executeQuery();
                while (resultSet.next()) {
                    receiveFromWhereOfNode.add(resultSet.getInt("from_where"));
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return receiveFromWhere;
    }

    // 数据整理
    // public static Map<String, List<Integer>>
    // 统计数据
    public static Map<String, List<Integer>> statsValuableByNode(Map<String, List<Integer>> latecyMapByNode) {
        Map<String, List<Integer>> out = new ConcurrentHashMap<>();
        List<Integer> outListOfNode;
        for (String database : getSortedList(latecyMapByNode.keySet())) {
            List<Integer> latecyList = latecyMapByNode.get(database);
            outListOfNode = new ArrayList<>();
            if (latecyList.size() == 0) {
                outListOfNode.add(-1);
                outListOfNode.add(-1);
                outListOfNode.add(-1);
                out.put(database, outListOfNode);
                continue;
            }
            outListOfNode.add(latecyList.stream().reduce(Integer::sum).orElse(0) / latecyList.size());
            outListOfNode.add(Collections.min(latecyList));
            outListOfNode.add(Collections.max(latecyList));
            out.put(database, outListOfNode);
        }
        return out;

    }

    public static List<Integer> statsValuableOverall(Map<String, List<Integer>> valuableStatsOutByNode) {
        List<Integer> out = new ArrayList<>();
        List<Integer> latecyList = new ArrayList<>();
        List<Integer> minList = new ArrayList<>();
        List<Integer> maxList = new ArrayList<>();
        for (List<Integer> valuableStatsOutOfNode : valuableStatsOutByNode.values()) {
            if (valuableStatsOutOfNode.get(0) == -1) {
                continue;
            }
            latecyList.add(valuableStatsOutOfNode.get(0));
            minList.add(valuableStatsOutOfNode.get(1));
            maxList.add(valuableStatsOutOfNode.get(2));
        }
        if (latecyList.size() != 0) {
            out.add(latecyList.stream().reduce(Integer::sum).orElse(0) / latecyList.size());
            out.add(Collections.min(latecyList));
            out.add(Collections.max(latecyList));
        }
        return out;
    }

    // 输出结果
    public static void outputLatecyByNode(Map<String, List<Integer>> latecyMapByNode,
            String outFilePathString) {
        try {
            FileWriter fileWriter = new FileWriter(outFilePathString);
            for (String database : getSortedList(latecyMapByNode.keySet())) {
                List<Integer> latecyList = latecyMapByNode.get(database);
                if (latecyList.size() == 0) {
                    fileWriter.write(database
                            + " latecyList size = 0"
                            + "\n");
                    continue;
                }
                fileWriter.write(database + ": "
                        + latecyList.stream().reduce(Integer::sum).orElse(0) / latecyList.size()
                        + "\n");
            }
            fileWriter.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void outputLatecyAndFromWhereByNode(Map<String, List<Integer>> latecyMapByNode,
            Map<String, Set<Integer>> receiveFromWhere,
            String outFilePathString) {
        try {
            FileWriter fileWriter = new FileWriter(outFilePathString);
            for (String database : getSortedList(latecyMapByNode.keySet())) {
                List<Integer> latecyList = latecyMapByNode.get(database);
                if (latecyList.size() == 0) {
                    fileWriter.write(database
                            + " latecyList size = 0"
                            + "\n");
                    continue;
                }
                fileWriter.write(database + ": "
                        + latecyList.stream().reduce(Integer::sum).orElse(0) / latecyList.size()
                        + ", received message from " + setToString(receiveFromWhere.get(database))
                        + "\n");
            }
            fileWriter.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void outputLatecyByRound(Map<String, List<Integer>> latecyMapByNode,
            List<Set<Integer>> nodeListByRound,
            String outFilePathString) {
        try {
            FileWriter fileWriter = new FileWriter(outFilePathString);
            for (int i = 0; i < nodeListByRound.size(); i++) {
                fileWriter.write("Round " + i + ":\n");
                Set<Integer> currentRoundNodelist = nodeListByRound.get(i);
                List<Integer> clist = new ArrayList<>();
                clist.addAll(currentRoundNodelist);
                Collections.sort(clist);
                for (int id : clist) {
                    String databaString = getDatabaseStringById(latecyMapByNode.keySet(), id);
                    List<Integer> latecyList = latecyMapByNode.get(databaString);
                    if (latecyList.size() == 0) {
                        fileWriter.write("node " + id
                                + " latecyList size = 0"
                                + "\n");
                        continue;
                    }
                    fileWriter.write("node " + id + " "
                            + latecyList.stream().reduce(Integer::sum).orElse(0) / latecyList.size()
                            + "\n");
                    // fileWriter.write("node " + id
                    //         + latecyList
                    //         + "\n");
                }
            }
            fileWriter.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void outputStatsByRound(Map<String, List<Integer>> latecyMapByNode,
            List<Set<Integer>> nodeListByRound,
            String outFilePathString) {
        try {
            FileWriter fileWriter = new FileWriter(outFilePathString);
            for (int i = 0; i < nodeListByRound.size(); i++) {

                List<Integer> latecyListByRound = new ArrayList<>();
                Set<Integer> currentRoundNodelist = nodeListByRound.get(i);
                List<Integer> clist = new ArrayList<>();
                clist.addAll(currentRoundNodelist);
                Collections.sort(clist);
                for (int id : clist) {
                    String databaString = getDatabaseStringById(latecyMapByNode.keySet(), id);
                    List<Integer> latecyList = latecyMapByNode.get(databaString);
                    if (latecyList.size() == 0) {
                        continue;
                    }
                    latecyListByRound.add(latecyList.stream().reduce(Integer::sum).orElse(0) / latecyList.size());
                }
                if (latecyListByRound.size() == 0) {
                    fileWriter.write("Round " + i + " latecyListByRound.size() = 0:\n");
                    continue;
                }
                fileWriter.write("Round " + i + ":\n"
                        + "average: " + latecyListByRound.stream().reduce(Integer::sum).orElse(0) / latecyListByRound.size() + "\n"
                        + "min: " + Collections.min(latecyListByRound) + "\n"
                        + "max: " + Collections.max(latecyListByRound) + "\n");
            }
            fileWriter.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void outputOverallStatsOut(List<Integer> overallStatsOut, String outFilePathString) {
        try {
            FileWriter fileWriter = new FileWriter(outFilePathString);
            if (overallStatsOut.size() == 0) {
                fileWriter.write("Not valuble Overall stats out\n");
            } else {
                fileWriter.write("Overall Average latecy: " + overallStatsOut.get(0)
                        + "\nMin node average lactecy: " + overallStatsOut.get(1)
                        + "\nMax node average lactecy: " + overallStatsOut.get(2)
                        + "\n");
            }
            fileWriter.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
